#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "pd_linux.h"
#include "config_cp210x.h"
#include "libusb.h"
#include "xlibusb.h"

namespace config_cp210x
{

    const int CP210x_PURGE = 0x12;
    const int CP210x_IFC_ENABLE = 0x00;
    const int CP210x_SET_BAUDDIV = 0x01;
    const int CP210x_SET_LINE_CTL = 0x03;
    const int CP210x_GET_LINE_CTL = 0x04;
    const int CP210X_SET_BREAK = 0x05;
    const int CP210x_SET_MHS = 0x07;
    const int CP210x_SET_BAUDRATE = 0x1E;
    const int CP210x_SET_FLOW = 0x13;
    const int CP210x_SET_XON = 0x09;
    const int CP210x_SET_XOFF = 0x0A;
    const int CP210x_SET_CHARS = 0x19;
    const int CP210x_GET_MDMSTS = 0x08;
    const int CP210x_GET_COMM_STATUS = 0x10;

    const int CP210x_REQTYPE_HOST2DEVICE = 0x41;
    const int CP210x_REQTYPE_DEVICE2HOST = 0xC1;

    const int CP210x_BREAK_ON = 0x0001;
    const int CP210x_BREAK_OFF = 0x0000;

    const int CP210x_MHS_RTS_ON = 0x202;
    const int CP210x_MHS_RTS_OFF = 0x200;
    const int CP210x_MHS_DTR_ON = 0x101;
    const int CP210x_MHS_DTR_OFF = 0x100;

    const int CP210x_PURGE_ALL = 0x000f;

    /***
     *  Default Serial Configuration
     *  Baud rate: 9600
     *  Data bits: 8
     *  Stop bits: 1
     *  Parity: None
     *  Flow Control: Off
     */
    const int CP210x_UART_ENABLE = 0x0001;
    const int CP210x_UART_DISABLE = 0x0000;
    const int CP210x_LINE_CTL_DEFAULT = 0x0800;
    const int CP210x_MHS_DEFAULT = 0x0000;
    const int CP210x_MHS_DTR = 0x0001;
    const int CP210x_MHS_RTS = 0x0010;
    const int CP210x_MHS_ALL = 0x0011;
    const int CP210x_XON = 0x0000;
    const int CP210x_XOFF = 0x0000;
    const int DEFAULT_BAUDRATE = 9600;

    const int DATA_BITS_5 = 5;
    const int DATA_BITS_6 = 6;
    const int DATA_BITS_7 = 7;
    const int DATA_BITS_8 = 8;

    const int STOP_BITS_1 = 1;
    const int STOP_BITS_15 = 3;
    const int STOP_BITS_2 = 2;

    const int PARITY_NONE = 0;
    const int PARITY_ODD = 1;
    const int PARITY_EVEN = 2;
    const int PARITY_MARK = 3;
    const int PARITY_SPACE = 4;

    const int FLOW_CONTROL_OFF = 0;
    const int FLOW_CONTROL_RTS_CTS = 1;
    const int FLOW_CONTROL_DSR_DTR = 2;
    const int FLOW_CONTROL_XON_XOFF = 3;

    unsigned char dataOff[] =
    {
        (unsigned char) 0x01, (unsigned char) 0x00, (unsigned char) 0x00, (unsigned char) 0x00,
        (unsigned char) 0x40, (unsigned char) 0x00, (unsigned char) 0x00, (unsigned char) 0x00,
        (unsigned char) 0x00, (unsigned char) 0x80, (unsigned char) 0x00, (unsigned char) 0x00,
        (unsigned char) 0x00, (unsigned char) 0x20, (unsigned char) 0x00, (unsigned char) 0x00
    };
    unsigned char dataRTSCTS[]
    {
        (unsigned char) 0x09, (unsigned char) 0x00, (unsigned char) 0x00, (unsigned char) 0x00,
        (unsigned char) 0x40, (unsigned char) 0x00, (unsigned char) 0x00, (unsigned char) 0x00,
        (unsigned char) 0x00, (unsigned char) 0x80, (unsigned char) 0x00, (unsigned char) 0x00,
        (unsigned char) 0x00, (unsigned char) 0x20, (unsigned char) 0x00, (unsigned char) 0x00
    };
    unsigned char dataDSRDTR[]
    {
        (unsigned char) 0x11, (unsigned char) 0x00, (unsigned char) 0x00, (unsigned char) 0x00,
        (unsigned char) 0x40, (unsigned char) 0x00, (unsigned char) 0x00, (unsigned char) 0x00,
        (unsigned char) 0x00, (unsigned char) 0x80, (unsigned char) 0x00, (unsigned char) 0x00,
        (unsigned char) 0x00, (unsigned char) 0x20, (unsigned char) 0x00, (unsigned char) 0x00
    };

    unsigned char dataXONXOFF[]
    {
        (unsigned char) 0x01, (unsigned char) 0x00, (unsigned char) 0x00, (unsigned char) 0x00,
        (unsigned char) 0x43, (unsigned char) 0x00, (unsigned char) 0x00, (unsigned char) 0x00,
        (unsigned char) 0x00, (unsigned char) 0x80, (unsigned char) 0x00, (unsigned char) 0x00,
        (unsigned char) 0x00, (unsigned char) 0x20, (unsigned char) 0x00, (unsigned char) 0x00
    };

    unsigned char dataChars[]
    {
        (unsigned char) 0x00, (unsigned char) 0x00, (unsigned char) 0x00,
        (unsigned char) 0x00, (unsigned char) 0x11, (unsigned char) 0x13
    };

    int setControlCommand(void *handle, int request, int value, unsigned char *data, int dataLength)
    {
        int response = x_libusb_controltransfer(handle, CP210x_REQTYPE_HOST2DEVICE, request, value, 0, data, dataLength, 1000);
        return response;
    }

    unsigned char getModemState(void *handle)
    {
        unsigned char data = 0;
        x_libusb_controltransfer(handle, CP210x_REQTYPE_DEVICE2HOST, CP210x_GET_MDMSTS, 0, 0, &data, 1, 1000);
        return data;
    }

    int  getCommStatus(void *handle, unsigned char *data, int length)
    {
        int response = x_libusb_controltransfer(handle, CP210x_REQTYPE_DEVICE2HOST, CP210x_GET_COMM_STATUS, 0, 0, data, 19, 1000);
        return response;
    }

    short getCTL(void *handle)
    {
        unsigned char data[2] = {0x0};
        int response = x_libusb_controltransfer(handle, CP210x_REQTYPE_DEVICE2HOST, CP210x_GET_LINE_CTL, 0, 0, data, 2, 1000);
        return (short)((data[1] << 8) | (data[0] & 0xFF));
    }

    int Init(void *handle)
    {
        if(setControlCommand(handle, CP210x_IFC_ENABLE, CP210x_UART_ENABLE, NULL, 0) < 0)
        {
            return -1;
        }
        SetBaudRate(handle, 115200);
        if(setControlCommand(handle, CP210x_SET_LINE_CTL, CP210x_LINE_CTL_DEFAULT, NULL, 0) < 0)
        {
            return -1;
        }
        SetFlowControl(handle, FLOW_CONTROL_OFF);
        if(setControlCommand(handle, CP210x_SET_MHS, CP210x_MHS_DEFAULT, NULL, 0) < 0)
        {
            return -1;
        }
        return 0;
    }

    void Free(void *handle)
    {
        setControlCommand(handle, CP210x_PURGE, CP210x_PURGE_ALL, NULL, 0);
        setControlCommand(handle, CP210x_IFC_ENABLE, CP210x_UART_DISABLE, NULL, 0);
    }

    void SetBaudRate(void *handle, int baudRate)
    {
        unsigned char data[]
        {
            (unsigned char)(baudRate & 0xff),
            (unsigned char)(baudRate >> 8 & 0xff),
            (unsigned char)(baudRate >> 16 & 0xff),
            (unsigned char)(baudRate >> 24 & 0xff)
        };
        setControlCommand(handle, CP210x_SET_BAUDRATE, 0, data, 4);
    }

    void SetParity(void *handle, int parity)
    {
        short wValue = getCTL(handle);
        wValue &= ~0x00F0;
        switch(parity)
        {
            case PARITY_NONE:
                wValue |= 0x0000;
                break;
            case PARITY_ODD:
                wValue |= 0x0010;
                break;
            case PARITY_EVEN:
                wValue |= 0x0020;
                break;
            case PARITY_MARK:
                wValue |= 0x0030;
                break;
            case PARITY_SPACE:
                wValue |= 0x0040;
                break;
            default:
                return;
        }
        setControlCommand(handle, CP210x_SET_LINE_CTL, wValue, NULL, 0);
    }

    void SetFlowControl(void *handle, int flowControl)
    {

        switch(flowControl)
        {
            case FLOW_CONTROL_OFF:
                setControlCommand(handle, CP210x_SET_FLOW, 0, dataOff, 16);
                break;
            case FLOW_CONTROL_RTS_CTS:
                setControlCommand(handle, CP210x_SET_FLOW, 0, dataRTSCTS, 16);
                setControlCommand(handle, CP210x_SET_MHS, CP210x_MHS_RTS_ON, NULL, 0);
                break;
            case FLOW_CONTROL_DSR_DTR:
                setControlCommand(handle, CP210x_SET_FLOW, 0, dataDSRDTR, 16);
                setControlCommand(handle, CP210x_SET_MHS, CP210x_MHS_DTR_ON, NULL, 0);
                break;
            case FLOW_CONTROL_XON_XOFF:
                setControlCommand(handle, CP210x_SET_CHARS, 0, dataChars, 8);
                setControlCommand(handle, CP210x_SET_FLOW, 0, dataXONXOFF, 16);
                break;
            default:
                return;
        }
    }

    void SetDataBits(void *handle, int dataBits)
    {
        short wValue = getCTL(handle);
        wValue &= ~0x0F00;
        switch(dataBits)
        {
            case DATA_BITS_5:
                wValue |= 0x0500;
                break;
            case DATA_BITS_6:
                wValue |= 0x0600;
                break;
            case DATA_BITS_7:
                wValue |= 0x0700;
                break;
            case DATA_BITS_8:
                wValue |= 0x0800;
                break;
            default:
                return;
        }
        setControlCommand(handle, CP210x_SET_LINE_CTL, wValue, NULL, 0);
    }

    void SetStopBits(void *handle, int stopBits)
    {
        short wValue = getCTL(handle);
        wValue &= ~0x0003;
        switch(stopBits)
        {
            case STOP_BITS_1:
                wValue |= 0;
                break;
            case STOP_BITS_15:
                wValue |= 1;
                break;
            case STOP_BITS_2:
                wValue |= 2;
                break;
            default:
                return;
        }
        setControlCommand(handle, CP210x_SET_LINE_CTL, wValue, NULL, 0);
    }

    void SetBreak(void *handle, int state)
    {
        if(state == 1)
        {
            setControlCommand(handle, CP210X_SET_BREAK, CP210x_BREAK_ON, NULL, 0);
        }
        else
        {
            setControlCommand(handle, CP210X_SET_BREAK, CP210x_BREAK_OFF, NULL, 0);
        }
    }
};
